﻿using System;
using SmartCard.Framework;

namespace SmartCard.PcscApi
{
	class PcscSmartCard : ICard
	{
		#region fields

		private PcscConnectionContext _connectionContext = null;

		#endregion

		#region properties

		public ISmartCardReader Reader
		{ get; private set; }

		public IFeatureCollection Features
		{ get; private set; }

		public byte[] AnswerToReset
		{ get; private set; }

		#endregion

		#region construction

		public PcscSmartCard(PcscSmartCardReader reader, byte[] answerToReset)
		{
			Reader = reader;
			AnswerToReset = answerToReset; 
			Features = new MefFeatureCollection(this);
		}

		#endregion

		#region public methods

		public void Connect(ConnectionOptions options)
		{
			// if we are already connected, then we will not do anything.
			if (_connectionContext == null)
			{
				var pcscReader = (PcscSmartCardReader)Reader;

				uint cardConnectionHandle = 0;
				uint activeProtocol = 0;

				var ret = PcscInvokes.SCardConnect(pcscReader.InitializationContext,
									pcscReader.FriendlyName,
									ConvertToPcscShareMode(options.ShareMode),
									ConvertToPcscProtocol(options.ConnectionProtocol),
									out cardConnectionHandle, out activeProtocol);

				if (((ePcscErrors)ret) != (ePcscErrors.Success))
				{ throw new PcscException("failed to connect", (ePcscErrors)ret); }

				_connectionContext = new PcscConnectionContext(
					cardConnectionHandle, (ePcscProtocol)activeProtocol);
			}
		}

		public void Disconnect(DisconnectOptions options)
		{
			// if already disconnected, no need to do anything
			if (_connectionContext != null)
			{
				var tempHandle = _connectionContext;
				_connectionContext = null;

				var ret = PcscInvokes.SCardDisconnect(tempHandle.ConnectionHandle, 0);

				if (((ePcscErrors)ret) != (ePcscErrors.Success))
				{ throw new PcscException("failed to disconnect", (ePcscErrors)ret); }
			}
		}

		public ApduResponse Send(ApduCommand request)
		{
			if (request == null)
			{ throw new ArgumentNullException("request"); }

			if (_connectionContext == null)
			{ throw new SmartCardException("card currently disconnected."); }

			var ioRequest = new SCard_IO_Request();
			ioRequest.m_dwProtocol = (uint)_connectionContext.ConnectionProtocol;
			ioRequest.m_cbPciLength = 8;

			// must add two bytes for max response length to include SW1/SW2 response
			uint responseDataLength = (uint)(request.MaxResponseLength + 2);
			byte[] responseData = new byte[responseDataLength];

			var rawApdu = request.GetRaw(true);
			var ret = PcscInvokes.SCardTransmit(
				_connectionContext.ConnectionHandle, ref ioRequest,
				rawApdu, (uint)rawApdu.Length, IntPtr.Zero,
				responseData, out responseDataLength);

			if ((ePcscErrors)ret != ePcscErrors.Success)
			{ throw new PcscException("Failed to transmit APDU", (ePcscErrors)ret); }

			// return the response
			return new ApduResponse(responseData,
				(ushort) responseDataLength);
		}

		#endregion

		#region privates

		private uint ConvertToPcscShareMode(eCardConnectionShareMode shareMode)
		{
			ePcscShareMode pcscShareMode;

			switch (shareMode)
			{
				case eCardConnectionShareMode.Direct:
					pcscShareMode = ePcscShareMode.Direct;
					break;
				case eCardConnectionShareMode.Exclusive:
					pcscShareMode = ePcscShareMode.Exclusive;
					break;
				case eCardConnectionShareMode.Shared:
					pcscShareMode = ePcscShareMode.Shared;
					break;
				default:
					throw new NotSupportedException(
						string.Format("shareMode [{0}] is not supported with PC/SC API", shareMode));
			}

			return (uint)pcscShareMode;
		}

		private eCardConnectionShareMode ConvertToFrameworkShareMode(ePcscShareMode shareMode)
		{
			throw new NotImplementedException();
		}

		private uint ConvertToPcscProtocol(eCardConnectionProtocol protocol)
		{
			ePcscProtocol pcscProtocol = ePcscProtocol.Undefined;

			if ((protocol & eCardConnectionProtocol.T0) == eCardConnectionProtocol.T0)
			{ pcscProtocol |= ePcscProtocol.T0; }

			if ((protocol & eCardConnectionProtocol.T1) == eCardConnectionProtocol.T1)
			{ pcscProtocol |= ePcscProtocol.T1; }

			return (uint)pcscProtocol;
		}

		private eCardConnectionProtocol ConvertToFrameworkConnectionProtocol(eCardConnectionProtocol protocol)
		{
			throw new NotImplementedException();
		}

		#endregion
	}
}
